package org.kineticsproject.visualizations;

import java.awt.Color;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.jfree.chart.JFreeChart;

import org.kineticsproject.devices.parsers.UnitConverter;


import data.DataLog;
import data.DataSample;
import data.Exercise;
import data.FusedDataSample;

public class VisualizationGenerator {

	static final int ALL_DEVICES = 4;

	static final int INVALID_ELEVATION_VALUE = 32767;

	static final int INVALID_DISPLACEMENT_VALUE = 32767;


	private long movingTime;

	private double elevationGain;

	private double elevationLoss;

	private long lastSampleTime;

	private double lastSampleSpeed;

	private double lastSampleElevation; // last elevation sample for calculating
										// elevation gain

	private double lastSampleElevationGrade; // last elevation sample for
												// calculating grade
	private float lastSampleDisplacement;

	private float displacementAscent;

	private float displacementDescent;

	private float displacementFlat;

	private double maximumGradeAscent;

	private double maximumGradeDescent;

	
	public JFreeChart generate(Exercise ex)
	{
		//super();

		
		List<XYSeriesExt> xyDistanceSeries;
		List<XYSeriesExt> xyAltitudeSeries;
		List<XYSeriesExt> xySpeedSeries;
		List<XYSeriesExt> xyHeartRateSeries;

		
		
		//MultiplePlotChart multipleChart = new MultiplePlotChart();
		HeartRatePieChart hrPieChart = new HeartRatePieChart();
	//	visualizations.GradePieChart gradePieChart = new visualizations.GradePieChart();
		//SummaryPanel summaryPanel = new SummaryPanel();

		int hrMax;
		int otherCount = 0;
		int aerobicCount = 0;
		int anaerobicCount = 0;
		int recoveryCount = 0;
		int vo2maxCount = 0;
		int totalCount = 0;


		XYSeriesExt hrDistanceSeries = new XYSeriesExt("Heart Rate", "Miles",
				"Beats/Minute", "Distance", "Heart Rate", Color.red, Color.red);
		XYSeriesExt hrAltitudeSeries = new XYSeriesExt("Heart Rate", "Feet",
				"Beats/Minute", "Altitude", "Heart Rate", Color.red, Color.red);
		XYSeriesExt hrSpeedSeries = new XYSeriesExt("Heart Rate", "Miles/Hour",
				"Beats/Minute", "Speed", "Heart Rate", Color.red, Color.red);


		XYSeriesExt altDistanceSeries = new XYSeriesExt("Altitude", "Feet",
				"Feet", "Distance", "Altitude", Color.blue, Color.blue);
		XYSeriesExt altSpeedSeries = new XYSeriesExt("Altitude", "Miles/Hour",
				"Feet", "Speed", "Altitude", Color.blue, Color.blue);


		XYSeriesExt speedDistanceSeries = new XYSeriesExt("Speed", "Miles",
				"Miles/Hour", "Distance", "Speed", Color.black, Color.black);
		XYSeriesExt speedAltitudeSeries = new XYSeriesExt("Speed", "Feet",
				"Miles/Hour", "Altitude", "Speed", Color.black, Color.black);


		XYSeriesExt distanceAltitudeSeries = new XYSeriesExt("Distance",
				"Feet", "Miles", "Altitude", "Distance", Color.green,
				Color.green);
		XYSeriesExt distanceSpeedSeries = new XYSeriesExt("Distance",
				"Miles/Hour", "Miles", "Speed", "Distance", Color.green,
				Color.green);

		//rwProperties = new RootWindowProperties();

		//exStats = new ExerciseStatistics();
		hrMax = 220 - 28;
		movingTime = 0;
		elevationGain = 0.0;
		elevationLoss = 0.0;
		displacementAscent = 0;
		displacementDescent = 0;
		displacementFlat = 0;
		maximumGradeAscent = 0.0;
		maximumGradeDescent = 0.0;
		lastSampleElevation = INVALID_ELEVATION_VALUE;
		lastSampleElevationGrade = INVALID_ELEVATION_VALUE;
		lastSampleDisplacement = INVALID_DISPLACEMENT_VALUE;

		FusedDataSample fsample;

		Iterator<DataSample> fdlIT = ex.getFusedDataLog().getDataSamples().iterator();
		
		fsample = (FusedDataSample) fdlIT.next();

		int i = 0;
		double speedSample = 0;
		float totalDisplacement = 0;
		double filteredTotalDisp = 0;

		while (fdlIT.hasNext())
		{

			fsample = (FusedDataSample) fdlIT.next();
			
			if (fsample.getDisplacementsample() != null)
			{
				// System.out.println(totalDisplacement);
				totalDisplacement = totalDisplacement
						+ fsample.getDisplacementsample().getDisplacement();

				calculateGrade(fsample.getPositionsample().getAltitude(),
						fsample.getDisplacementsample().getDisplacement()); // protect
																			// this
																			// in
																			// case
																			// displacement
																			// and
																			// position
																			// data
																			// is
																			// not
																			// available

				speedSample = UnitConverter.lowpassfilter(0.05, speedSample,
						fsample.getDisplacementsample().getSpeed());

				speedDistanceSeries.add(UnitConverter
						.FeettoMiles(totalDisplacement), UnitConverter
						.KilometerstoMiles(speedSample));
				speedAltitudeSeries.add((fsample.getPositionsample()
						.getAltitude()), UnitConverter
						.KilometerstoMiles(speedSample));
				calculateMovingTime(fsample.getTimestamp().getTime(),
						speedSample);

				// filteredTotalDisp = UnitConverter.lowpassfilter(0.001,
				// filteredTotalDisp,
				// parser.UnitConverter.FeettoMiles(totalDisplacement));

				// distanceTimeSeries.add(fsample.getTimestamp().getTime(),filteredTotalDisp);

				// distanceTimeSeries.add(fsample.getTimestamp().getTime(),UnitConverter.lowpassfilter(0.05parser.UnitConverter.FeettoMiles(totalDisplacement));
				distanceAltitudeSeries.add((fsample.getPositionsample()
						.getAltitude()), UnitConverter
						.FeettoMiles(totalDisplacement));
				distanceSpeedSeries.add(speedSample, UnitConverter
						.FeettoMiles(totalDisplacement));
			}

			if (fsample.getPositionsample() != null)
			{

				altDistanceSeries.add(UnitConverter
						.FeettoMeters(totalDisplacement), fsample
						.getPositionsample().getAltitude());
				altSpeedSeries.add(speedSample, fsample.getPositionsample()
						.getAltitude());
				calculateElevationChange(fsample.getPositionsample()
						.getAltitude());
			}

			
		}

		

		hrDistanceSeries.calculateStatistics();
		hrAltitudeSeries.calculateStatistics();
		hrSpeedSeries.calculateStatistics();

		
		speedDistanceSeries.calculateStatistics();
		speedAltitudeSeries.calculateStatistics();

		
		altDistanceSeries.calculateStatistics();
		altSpeedSeries.calculateStatistics();


		distanceAltitudeSeries.calculateStatistics();
		distanceSpeedSeries.calculateStatistics();

		/* Populate Exercise Stats */

		ex.setDistance(UnitConverter
						.FeettoMiles(totalDisplacement));

		ex.setMovingTime(movingTime);
		ex.setMovingTimePace((long) (movingTime / ex.getDistance()));
		ex.setTotalTime(ex.getFusedDataLog().getEndTime().getTime()
				- ex.getFusedDataLog().getStartTime().getTime());
		ex.setTotalTimePace((long) (ex.getTotalTime() / ex
				.getDistance()));
		ex.setMovingSpeedAverage((double) ex.getDistance()
				/ (((double) ex.getMovingTime()) / 1000.0 / 60 / 60));
		//ex.setMovingSpeedMaximum(speedTimeSeries.getMaximum());

		//ex.setElevationStart(altTimeSeries.getFirst());
		//ex.setElevationFinish(altTimeSeries.getLast());
		ex.setElevationGain(elevationGain);
		ex.setElevationLoss(elevationLoss);
		ex.setAscentSpeedAverage(elevationGain
				/ (ex.getTotalTime() / 1000.0 / 60)); // convert time from
															// ms to minutes
															// before dividing
															// (result is
															// ft/min)
		ex.setDescentSpeedAverage(elevationLoss
				/ (ex.getTotalTime() / 1000.0 / 60));
		//ex.setElevationMaximum(altTimeSeries.getMaximum());
		//ex.setElevationMinimum(altTimeSeries.getMinimum());
		ex
				.setAscentGradeAverage((ex.getElevationGain() / displacementAscent));
		ex
				.setDescentGradeAverage((ex.getElevationGain() / displacementDescent));
		ex.setAscentGradeMaximum(this.maximumGradeAscent);
		ex.setDescentGradeMaximum(this.maximumGradeDescent);
		ex.setDistanceAscent(displacementAscent);
		ex.setDistanceDescent(displacementDescent);
		ex.setPercentAscent(((double) displacementAscent)
				/ totalDisplacement);
		ex.setPercentDescent(((double) displacementDescent)
				/ totalDisplacement);
		ex.setPercentFlat(1.0 - ex.getPercentAscent()
				- ex.getPercentDescent());

		/*
		 * System.out.println("displacementAscent:
		 * "+exStats.getDistanceAscent()); System.out.println("Distance:
		 * "+exStats.getDistance()); System.out.println("Percent Ascent:
		 * "+exStats.getPercentAscent());
		 */


		xyDistanceSeries = new LinkedList<XYSeriesExt>();
		xyDistanceSeries.add(hrDistanceSeries);
		xyDistanceSeries.add(speedDistanceSeries);
		xyDistanceSeries.add(altDistanceSeries);

		xyAltitudeSeries = new LinkedList<XYSeriesExt>();
		xyAltitudeSeries.add(hrAltitudeSeries);
		xyAltitudeSeries.add(speedAltitudeSeries);
		xyAltitudeSeries.add(distanceAltitudeSeries);

		xySpeedSeries = new LinkedList<XYSeriesExt>();
		xySpeedSeries.add(hrSpeedSeries);
		xySpeedSeries.add(distanceSpeedSeries);
		xySpeedSeries.add(altSpeedSeries);

		

		/* Exercise Summary */
		/*JTable summaryTable = new SummaryTable(new SummaryTableModel(ex));

		summaryPanel.setTable(summaryTable);
		summaryPanel.setDisplayPlotSelection(true);
		summaryPanel.setDisplayTable(true);
		summaryPanel.setExtraPanel(gradePieChart.setupChart(ex,
				"Grade Stats", "Grade Stats"));
		summaryPanel.setDisplayExtraPanel(true);
		//summaryPanel.setFusedDataLog(fDataLog);
*/
		// chartpanelList.add(summaryPanel.setupChart(xyTimeSeries,"Summary","Summary"));


		// chartpanelList.add(multipleChart.setupChart(xyTimeSeries, "Time Series", "Time Series", new
		// org.jfree.chart.renderer.xy.StandardXYItemRenderer()));
		// chartpanelList.add(multipleChart.setupChart(xyDistanceSeries,
		// "Distance Series", "Distance Series", new
		// org.jfree.chart.renderer.xy.StandardXYItemRenderer()));
		// chartpanelList.add(hrPieChart.setupChart(ex,"HR Stats","HR Stats"));

		//chartpanelList.add(gradePieChart.setupChart(ex, "Grade Stats",
		//		"Grade Stats"));

		org.jfree.chart.renderer.xy.XYLineAndShapeRenderer renderer = new org.jfree.chart.renderer.xy.XYLineAndShapeRenderer();
		renderer.setSeriesOutlinePaint(0, Color.black);
		renderer.setUseOutlinePaint(true);
		renderer.setLinesVisible(false);

		// chartpanelList.add(multipleChart.setupChart(xyAltitudeSeries,"Altitude Series", "Altitude Series", renderer));
		// chartpanelList.add(multipleChart.setupChart(xySpeedSeries,"Speed Series","Speed Series", renderer));

		//initGUI(chartpanelList);
		
		return(hrPieChart.setupChart(ex, "hr chart"));
	}

	private void calculateGrade(float elevationSample, float displacementSample)
	{

		final double minimumElevationChange = 1; // 1 foot
		final double minimumDisplacement = 1; // 1 centimeters

		// displacementSample =
		// parser.UnitConverter.CentimeterstoFeet(displacementSample);

		if (lastSampleElevationGrade == INVALID_ELEVATION_VALUE)
			lastSampleElevationGrade = elevationSample;

		if (lastSampleDisplacement == INVALID_DISPLACEMENT_VALUE)
			lastSampleDisplacement = displacementSample;

		// if (((lastSampleElevationGrade-elevationSample) >
		// minimumElevationChange) )//&& (lastSampleDisplacement >
		// minimumDisplacement))
		if (lastSampleElevationGrade < elevationSample)
		{
			// elevationGain = elevationGain +
			// (elevationSample-lastSampleElevation);
			displacementAscent = displacementAscent + displacementSample;

			if (displacementSample > 0)
			{

				double currentGrade = ((elevationSample - lastSampleElevationGrade) / ((double) displacementSample));

				if (currentGrade > this.maximumGradeAscent)
					maximumGradeAscent = currentGrade;
			}
		}
		// else if (((lastSampleElevationGrade-elevationSample) <
		// -minimumElevationChange) )//&& (lastSampleDisplacement >
		// minimumDisplacement))
		else if (lastSampleElevationGrade > elevationSample)
		{
			// elevationLoss = elevationLoss -
			// (lastSampleElevation-elevationSample); // - (-) = a positive
			// value elevationLoss always +
			displacementDescent = displacementDescent + displacementSample;
			if (displacementSample > 0)
			{

				double currentGrade = ((lastSampleElevationGrade - elevationSample) / ((double) displacementSample));

				if (currentGrade > this.maximumGradeDescent)
					maximumGradeDescent = currentGrade;
			}
		} else
		{
			displacementFlat = displacementFlat + displacementSample;

		}
		lastSampleElevationGrade = elevationSample;

		if (displacementSample != 0) // if this is a interpolated
										// displacement sample do not update the
										// displacement sample
			lastSampleDisplacement = displacementSample;

		// displacementFlat = displacementFlat +
		// java.lang.Math.abs(displacementSample);
		
		

	}
	
	private void calculateMovingTime(long sampleTime, double sampleSpeed)
	{
		final double minimumSpeed = 0.01; // (meters/s) minimum speed to be
											// considered moving

		/*
		 * If faster than minimum speed calculate the time that has elapsed
		 * since the last sample and add it to the total
		 */
		if (lastSampleSpeed >= minimumSpeed)
			movingTime = movingTime + (sampleTime - lastSampleTime);

		/* Store the speed and time of the current sample for next calculation */
		lastSampleSpeed = sampleSpeed;
		lastSampleTime = sampleTime;
	}

	private void calculateElevationChange(double elevationSample)
	{
		final double minimumElevationChange = 1; // 1 foot

		if (lastSampleElevation == INVALID_ELEVATION_VALUE)
			lastSampleElevation = elevationSample;

		if ((lastSampleElevation - elevationSample) >= minimumElevationChange)
		// if (lastSampleElevation < elevationSample)
		{
			elevationGain = elevationGain
					+ (lastSampleElevation - elevationSample);
		} else if ((lastSampleElevation - elevationSample) <= -minimumElevationChange)
		// else if (lastSampleElevation > elevationSample)
		{
			elevationLoss = elevationLoss
					- (lastSampleElevation - elevationSample); /*
																 * - (-) = a
																 * positive
																 * value
																 * elevationLoss
																 * always +
																 */
		}

		lastSampleElevation = elevationSample;
	}
	
}
